require 'socket'
require 'csputil'
require 'stuntserver'
require 'stuntchksrv'
require 'j_sling_common'
require 'logger'

class JSlingServer
  TIMEOUT = 300
  MAXRECVLEN  = 1472
  def initialize(port,resp_)
    @directory={} #HashMap<URI,DirectoryEntry>
    @server = UDPSocket.new
    @timetoreset=Time.new+3600
    @server.bind( "", port )
    @server.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 ) 
    @responders=resp_
    p "start from:"+ resp_.to_s
  end
  def getSocketAddress
    @server.addr
  end
  def run
    Thread.new do
      serve
    end
  end
  def serve
    while true                         # Servers are permanent, so loop forever.
      
      payload, from_who = @server.recvfrom( MAXRECVLEN )
      from_addr=Util.socketAddress(from_who)[0].clone
      addr=from_addr
      # from_who[0] contains the protocol family id.
      # from_who[1] contains the remote port.
      # from_who[2] contains the remote IP name (if available).
      # from_who[3] contains the remote IP address.
      if(payload.length!=0)
        begin
          src=nil
          expires=nil
          msg = Util.bin2Object(payload)[0]
      p ["from:",from_addr,",msg:",msg]
          case msg.type.cspcspcsp
          when JSlingMessage::Type::JSLING_REGISTER
            src=@directory[msg.source.to_s]
            expires=Time.new+TIMEOUT #expires must be checked
=begin
			#replaced with another version
            if(src==nil || src.expires<Time.now)
              @directory[msg.source.to_s]= DirectoryEntry.new(msg.source,from_addr,expires)
              msg.response = Util.getReponse(JSlingMessage::Response::JSLING_SUCCESS.to_s)
              msg.payload = @responders
            elsif src.address.addr.address == from_addr.addr.address
              src.expires=expires
              #if(src.address[0].port != from_addr[0].port)
              src.address=from_addr
              msg.response = Util.getReponse(JSlingMessage::Response::JSLING_SUCCESS.to_s)
              msg.payload = @responders
            else
              msg.response = Util.getReponse(JSlingMessage::Response::JSLING_ERROR.to_s)
            end
=end
			#since our id is unique, we don't check two ids are duplication
            if(src==nil)
              @directory[msg.source.to_s]= DirectoryEntry.new(msg.source,from_addr,expires)
            else
              src.expires=expires
              src.address=from_addr
            end
            msg.response = Util.getReponse(JSlingMessage::Response::JSLING_SUCCESS.to_s)
            msg.payload = @responders
          when JSlingMessage::Type::JSLING_FORWARD
            dst = @directory[msg.response == nil ? msg.destination.to_s : msg.source.to_s]
            if(dst==nil || dst.expires<Time.now)
              msg.response = Util.getReponse(JSlingMessage::Response::JSLING_ERROR.to_s)
            else
              addr=dst.address;
            end
            
          when JSlingMessage::Type::JSLING_ROUTE
            src = @directory[msg.response == nil ? msg.source.to_s : msg.destination.to_s]
            dst = @directory[msg.response == nil ? msg.destination.to_s : msg.source.to_s]
            if src == nil || src.address.addr.address != from_addr.addr.address \
              || src.address.port!= from_addr.port || src.expires<Time.now
              # only allow registered sources to send multi-hop packets
              msg.response = Util.getReponse(JSlingMessage::Response::JSLING_ERROR.to_s)
            elsif dst == nil || dst.expires<Time.now
              # find authoritative proxy for destination
              begin
                addr = JSlingCommon.resolveProxy(msg.destination.to_s).clone;
              rescue
                addr = nil;
              end
              if (addr == nil)
                addr=from_addr
                msg.response = Util.getReponse(JSlingMessage::Response::JSLING_ERROR.to_s)
              else
                #trapezoid routing. prevent loops
                msg.type.cspcspcsp=JSlingMessage::Type::JSLING_FORWARD;
              end
            else
              # found in local table
              addr = dst.address
            end
          else #default return error
            msg.response = Util.getReponse(JSlingMessage::Response::JSLING_ERROR.to_s)
          end # of when
          sv=Socket.pack_sockaddr_in(addr.port,"127.127.127.127").sub(/\177\177\177\177/,[addr.addr.address].pack("N"))
          @server.send(Util.getBytes([msg]),0,sv)
          p ["to:",addr,",msg:",msg]
          #to clear directory buffer
          if (@timetoreset<Time.now)
          	@directory={}
          	@timetoreset=Time.now+3600
          end
        rescue 
        p "error:"+$!.to_s
        end #of begin
      end # of if
      
    end # of loop.
  end  
  class DirectoryEntry
  attr_accessor :id, :address, :expires
    def initialize id_,address_,exp_
      @id=id_#uri
      @address=address_ #InetSocketAddress
      @expires=exp_ #Time at which the entry expires. By default, 5 minutes after the last refresh message.
    end
  end
end
if $0==__FILE__ then
  #s1=STUNTServer.new("84.234.17.41", JSlingCommon.proxyPort+16+rand(10))
  s1=STUNTServer.new("84.234.17.41", 443)
  s2=STUNTServer.new("84.234.17.51", 443)
#  s0=STUNTCHKServer.new("84.234.17.41",12345)
#  s0.run
  #s2=STUNTServer.new("84.234.17.51", JSlingCommon.proxyPort+32+rand(10))
  s1.run
  s2.run
  s3=JSlingServer.new(JSlingCommon.proxyPort,Util.twoSocketAddress(s1.getSocketAddress,s2.getSocketAddress)[0])
  s3.serve

end